package com.ieng.kican.modules.security.config;

import com.ieng.kican.common.utils.IUtil;
import com.ieng.kican.modules.security.core.annotaion.AnonymousAuthorize;
import com.ieng.kican.modules.security.group.filter.AuthenticationTokenFilter;
import com.ieng.kican.modules.security.group.handler.JwtAccessDeniedHandler;
import com.ieng.kican.modules.security.group.handler.TokenAuthenticationEntryPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.util.*;

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private ApplicationContext applicationContext;
    @Autowired
    private JwtAccessDeniedHandler accessDeniedHandler;
    @Autowired
    private TokenAuthenticationEntryPoint authenticationEntryPoint;
    @Autowired
    private AuthenticationTokenFilter tokenFilter;
    @Autowired
    private CorsFilter corsFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        AccessStage accessStage = new AccessStage(applicationContext);
        Map<String, String[]> anonymousMap = accessStage.anonymous;

        http.csrf().disable()
                .exceptionHandling()
                .accessDeniedHandler(accessDeniedHandler).authenticationEntryPoint(authenticationEntryPoint)
                .and()
                .headers().frameOptions().disable()
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers(HttpMethod.GET,
                        "/*.html",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js").permitAll()
                .antMatchers("/swagger-resources/**").permitAll()
                .antMatchers("/webjars/**").permitAll()
                .antMatchers("/*/api-docs").permitAll()
                .antMatchers("/druid/**").permitAll()
                .antMatchers(HttpMethod.GET, anonymousMap.get(AnonMethods.GET.toString())).anonymous()
                .antMatchers(HttpMethod.POST, anonymousMap.get(AnonMethods.POST.toString())).anonymous()
                .antMatchers(anonymousMap.get(AnonMethods.ALL.toString())).anonymous()
                .antMatchers(HttpMethod.OPTIONS).permitAll()
                .anyRequest().authenticated();

        http.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class);
        http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

    private static class AccessStage {
        public Map<String, String[]> anonymous;

        public AccessStage(ApplicationContext applicationContext){
            Map<RequestMappingInfo, HandlerMethod> handlerMethods = applicationContext.getBean(RequestMappingHandlerMapping.class).getHandlerMethods();
            anonymous = anonymousMap(handlerMethods);
        }

        private Map<String, String[]> anonymousMap(Map<RequestMappingInfo, HandlerMethod> methodMap) {
            Set<String> gets = new LinkedHashSet<>();
            Set<String> posts = new LinkedHashSet<>();
            Set<String> all = new LinkedHashSet<>();

            for (Map.Entry<RequestMappingInfo, HandlerMethod> methodEntry : methodMap.entrySet()) {
                HandlerMethod method = methodEntry.getValue();
                AnonymousAuthorize annotation = method.getMethodAnnotation(AnonymousAuthorize.class);
                if (IUtil.isNotObject(annotation) && annotation.value()) {
                    RequestMappingInfo entryKey = methodEntry.getKey();
                    Set<RequestMethod> methods = entryKey.getMethodsCondition().getMethods();
                    Set<String> patterns = entryKey.getPatternsCondition().getPatterns();
                    if (methods.size() == 0) {
                        all.addAll(patterns);
                    } else {
                        if (methods.contains(RequestMethod.GET)) {
                            gets.addAll(patterns);
                        } else if (methods.contains(RequestMethod.POST)) {
                            posts.addAll(patterns);
                        }
                    }
                }
            }

            Map<String, String[]> setMap = new HashMap<>(3);
            setMap.put(AnonMethods.GET.toString(), gets.toArray(new String[0]));
            setMap.put(AnonMethods.POST.toString(), posts.toArray(new String[0]));
            setMap.put(AnonMethods.ALL.toString(), all.toArray(new String[0]));
            return setMap;
        }

    }

    private enum AnonMethods {
        GET,
        POST,
        ALL

    }
}
